跳到主要内容

MyBatis 结果集映射

类型别名

<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
<typeAlias alias="Post" type="domain.blog.Post"/>
<typeAlias alias="Section" type="domain.blog.Section"/>
<typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

结果集映射流程

resultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

本质原理类型于直接使用SQL的AS关键字

<select id="getUserById" parameterType="int" resultType="com.alsritter.pojo.User">
select name as username,pwd as password from users where id = #{id};
</select>
<resultMap id="UserMap" type="com.alsritter.pojo.User">
<!--column:数据库中的字段,property:实体类中的属性-->
<!--本质就是指定映射-->
<result column="id" property="id"/>
<result column="name" property="name"/>
<result column="pwd" property="password"/>
</resultMap>

<select id="getUserById" parameterType="int" resultMap="UserMap">
select * from users where id = #{id};
</select>

当 pojo 里面是对象时

<resultMap id="blogResult" type="Blog">
<!-- 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能 -->
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="pwd" property="password"/>
<!-- association – 一个复杂类型的关联;许多结果将包装成这种类型 -->
<association javaType="Author" property="author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
</association>
</resultMap>

使用注解的方式

参考资料 Spring Boot 实战 —— MyBatis(注解版)使用方法

也可以用在 @Results 中使用 id 来标识一个映射关系,然后可以用 @ResultMap 复用这个映射关系:

@Select("SELECT * FROM users")
@Results(id = "user", value = {
@Result(property = "userSex", column = "user_sex"),
@Result(property = "nickName", column = "nick_name")
})
@Select("SELECT * FROM users WHERE id = #{id}")
List<UserEntity> getAll();

@ResultMap("user")
@Select("SELECT * FROM users WHERE id = #{id}")
UserEntity getUserById(Integer id);

结果集映射到 Bean

如果数据库返回的字段名和数据元的字段名称相同,且 没有复杂的类型,则可以直接把结果集映射到一个数据元上(JavaBean)

这个也是默认的方式

@Data@ToString@AllArgsConstructor@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
public interface UserMapper {
//根据id查询用户
User getUserById(int id);
}
<select id="getUserById" parameterType="int" resultType="com.alsritter.pojo.User">
select * from users where id = #{id};
</select>
@Test
public void test(){
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User userById = mapper.getUserById(1);
System.out.println(userById);
}
}

结果集映射到 Map

如果数据元(JavaBean)的名称和数据库返回的字段名不同,则可以将所有的列映射到 HashMap 的键上的方式来接收数据集

如下,就是利用 HashMap 的特性 Map<String, Object>

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
public interface UserMapper {
//根据id查询用户
Map<String,Object> getUserById(int id);
}
<select id="getUserById" parameterType="int" resultType="map">
select * from users where id = #{id};
</select>
@Test
public void test(){
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> userById = mapper.getUserById(1);
userById.forEach((k,o)->{
System.out.println(k+"---"+o);
});
}
}

// ----结果--->
// name---李武
// id---1
// pwd---31876381

高级结果集映射(resultMap)

上面的两种方式都只能接收一些简单的数据类型,当涉及到复杂的数据类型时(例如数据元的属性是个对象时)就无能为力了,所以这种时候一般使用 Mybatis 提供的 resultMap 标签

<resultMap id="UserMap" type="com.alsritter.pojo.User">
<!--column:数据库中的字段,property:实体类中的属性-->
<!--本质就是指定映射-->
<result column="id" property="id"/>
<result column="name" property="name"/>
<result column="pwd" property="password"/>
</resultMap>

<select id="getUserById" parameterType="int" resultMap="UserMap">
select * from users where id = #{id};
</select>

当实体类的 属性是一个引用类型时 则需要对这个实现类嵌套映射

<resultMap id="blogResult" type="Blog">
<!-- 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能 -->
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="pwd" property="password"/>
<!-- association – 一个复杂类型的关联;许多结果将包装成这种类型 -->
<association javaType="Author" property="author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
</association>
</resultMap>

结果集的有参构造

如果要使用有参构造来创建对象可以使用 constructor 标签

* constructor - 用于在实例化类时,注入结果到构造方法中
* idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
* arg - 将被注入到构造方法的一个普通结果

实例:

<resultMap id="blogResult" type="Blog">
<constructor>
<idArg column="blog_id" javaType="int"/>
</constructor>
<!-- 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能 -->
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="pwd" property="password"/>
<!-- association – 一个复杂类型的关联;许多结果将包装成这种类型 -->
<association javaType="Author" property="author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
</association>
</resultMap>

注解结果集映射

注解 @Results@Result 来结果集映射

<mapper namespace="data.UserMapper">
<resultMap type="data.User" id="userResultMap">
<!-- 用id属性来映射主键字段 -->
<id property="id" column="user_id"/>
<!-- 用result属性来映射非主键字段 -->
<result property="userName" column="user_name"/>
</resultMap>
</mapper>

使用注解的形式

@Select("select * from t_user where user_name = #{userName}")
@Results(
@Result(property = "userId", column = "user_id"),
@Result(property = "userName", column = "user_name")
)
User getUserByName(@Param("userName") String userName);

association 和 collection 区别

association 是用来描述一对一,collection 是用来描述一对多

association 使用注意

注意:MyBatis 处理 association 相当于循环调用 sql ,这样的性能比较低下,尽量查出来用 Java 循环处理即可,或者交由前端处理

修改示例:

例如这里,同过 association 进行子查询

    <!-- Start:按时间分组查询 -->
<resultMap id="ApiCollectionProductVO" type="com.lckj.business.product.model.ApiCollectionProductVO">
<result column="time" property="time"/>
<association property="collectionProducts" column="{time=time, id=id}" select="com.lckj.business.product.mapper.CollectionProductMapper.getProductByTime"/>
</resultMap>

<select id="getApiProductGroupList" resultMap="ApiCollectionProductVO">
select distinct DATE_FORMAT(COLLECTION_TIME, '%Y/%m/%d') as time, USER_ID as id
from cm_collection_product
where USER_ID = #{id}
order by COLLECTION_TIME desc;
</select>


<select id="getProductByTime" resultType="com.lckj.business.product.model.CollectionProductQueryVO">
select cp.*,u.USER_NAME,pl.LOCK_NAME,ps.SWATCHES_NAME,p.PRODUCT_NAME,p.PRODUCT_CODE
from cm_collection_product cp
LEFT JOIN pub_user u ON cp.USER_ID = u.USER_ID
LEFT JOIN p_product_lock pl ON cp.LOCK_ID = pl.LOCK_ID
LEFT JOIN p_product_swatches ps ON cp.SWATCHES_ID = ps.SWATCHES_ID
LEFT JOIN p_product p ON cp.PRODUCT_ID = p.PRODUCT_ID
where DATE_FORMAT(cp.COLLECTION_TIME, '%Y/%m/%d') = #{time} and cp.USER_ID = #{id} and cp.DATA_FLAG = 1 order by cp.ADD_TIME desc;
</select>
<!-- End:按时间分组查询 -->

但是这样循环进行 SQL 查询其实效率是很低的,可以在应用层避免

上面的例子修改成应用层分组:

/**
* 查询分组信息
*
* @param userId 用户编号
* @param pagerVO 分页信息
* @return SpaceByTime 分组信息
*/
public PagerVO getListProductByTime(Integer userId, PagerVO pagerVO) {
// 这一块就不用管它了(自己封装的框架),总之就是一个普通的查询 List
SimpleCondition condition = new SimpleCondition();
condition.andEqual("cp.userId", userId);
condition.andEqual("cp.dataFlag", BaseConstant.DATA_FLAG_NORMAL);
condition.setQueryFields("cp.*,u.USER_NAME,pl.LOCK_NAME,ps.SWATCHES_NAME,p.PRODUCT_NAME");

DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd");
List<ApiCollectionProductVO> list = new ArrayList<>();
// 注:这里如果使用 for 循环效率和可读性(代码太长了...) 过低,所以使用 JDK8 提供的 stream 工具
// 按照收藏时间分组(因为无法在 SQL 里面按时间分组取得不同的结果集,所以将其提到应用层进行处理)
listUnion(condition, pagerVO).stream()
// 这个 x 是 ApiCollectionProductVO 对象,这里的作用就是筛选出收藏时间不为空的对象
.filter(x -> x.getCollectionTime() != null)
// 这里就是把 time 转成 "yyyy/MM/dd" 类型的字符串,然后这个 groupingBy 方法用它进行分组
.collect(Collectors.groupingBy(time -> dtf.format(time.getCollectionTime())))
// 遍历这个 List<Map> 集合,把结果集丢到上面的 list 集合里面
.forEach((timeStr, rlist) -> list.add(new ApiCollectionProductVO(timeStr, rlist)));

pagerVO.setTotalRows(list.size());
pagerVO.setList(list);
return pagerVO;
}

MyBatisPlus 通用枚举

参考资料 官方文档 通用枚举

就是对一些约定的参数进行映射,例如 flag 字段在数据库中是通过 int 类型存储的,0 代表 xx、1 代表 xxx,显然这样记很不方便,因此可以使用 “通用枚举” 将其转成枚举类型

例如这里添加一个字段,用 1、2 分别标识男女

alter table user
add sex int default 1 not null comment '1-男,2-女';

编写一个 SexEnum

public enum SexEnum implements IEnum<Integer> {
MAN(1, "男"),
WOMAN(2, "女");

private int value;

private String desc;

SexEnum(int value, String desc) {
this.value = value;
this.desc = desc;
}

@Override
public Integer getValue() {
return this.value;
}

@Override
public String toString() {
return this.desc;
}
}

然后就能在 Entity 里使用这个字段了

private SexEnum sex;

在配置文件中加上枚举包的路径

mybatis-plus:
# 支持统配符 * 或者 ; 分割
typeEnumsPackage: com.alsritter.*.enums

现在可以编写测试方法了

@Test
void selectManUserTest() {
// 甚至可以使用枚举做条件查询
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("sex", SexEnum.MAN);
List<User> userList = userMapper.selectList(wrapper);
userList.forEach(x -> log.info(x.toString()));
}